/**
 * @file os_port_threadx.c
 * @brief RTOS abstraction layer (threadx)
 *
 * @section License
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 *
 * Copyright (C) 2010-2020 Oryx Embedded SARL. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * @author Oryx Embedded SARL (www.oryx-embedded.com)
 * @version 1.9.8
 **/

//Switch to the appropriate trace level
#define TRACE_LEVEL TRACE_LEVEL_OFF

//Dependencies
#include <stdio.h>
#include <stdlib.h>
#include "os_port.h"
#include "debug.h"

#include "app/app_main.h"

/**
 * @brief Kernel initialization
 **/

void osInitKernel(void)
{
}


/**
 * @brief Start kernel
 **/

void osStartKernel(void)
{
   //Start the scheduler
   tx_kernel_enter();
}


/**
 * @brief Create a new task
 * @param[in] name A name identifying the task
 * @param[in] taskCode Pointer to the task entry function
 * @param[in] param A pointer to a variable to be passed to the task
 * @param[in] stackSize The initial size of the stack, in words
 * @param[in] priority The priority at which the task should run
 * @return If the function succeeds, the return value is a pointer to the
 *   new task. If the function fails, the return value is NULL
 **/

OsTask *osCreateTask(const char_t *name, OsTaskCode taskCode,
   void *param, size_t stackSize, int_t priority)
{
  TX_THREAD* task = TX_NULL;
	CHAR *pointer_task = TX_NULL;
	CHAR *pointer_stack = TX_NULL;

	/* Allocate the stack for thread 0.  */
	tx_byte_allocate(thread_get_pool(), (VOID **) &pointer_stack, stackSize, TX_NO_WAIT);
	if(pointer_stack == TX_NULL)
	{
		PRINTF_Dbg("allocate thread stack failed..\r\n");
		
		return NULL;
	}
	
	tx_byte_allocate(thread_get_pool(), (VOID **) &pointer_task, sizeof(TX_THREAD), TX_NO_WAIT);
	if(pointer_task == TX_NULL)
	{
		PRINTF_Dbg("allocate ailed..\r\n");
		
		return NULL;
	}
	task = (TX_THREAD *)pointer_task;
  //Create a new task
	tx_thread_create(task, (char *)name, taskCode, 0,  
					pointer_stack, stackSize, 
					priority, priority, TX_NO_TIME_SLICE, TX_AUTO_START);

	 return task;
}


/**
 * @brief Delete a task
 * @param[in] task Pointer to the task to be deleted
 **/

void osDeleteTask(OsTask *task)
{
  //Delete the specified task
  tx_thread_delete((TX_THREAD *) task);
	
	tx_byte_release(task);
}


/**
 * @brief Delay routine
 * @param[in] delay Amount of time for which the calling task should block
 **/

void osDelayTask(systime_t delay)
{
   //Delay the task for the specified duration
   tx_thread_sleep(OS_MS_TO_SYSTICKS(delay));
}


/**
 * @brief Yield control to the next task
 **/

void osSwitchTask(void)
{
   //Force a context switch
	tx_thread_relinquish();
}


/**
 * @brief Suspend scheduler activity
 **/

void osSuspendAllTasks(void)
{
   //Make sure the operating system is running
   tx_interrupt_control(TX_INT_DISABLE);
}


/**
 * @brief Resume scheduler activity
 **/

void osResumeAllTasks(void)
{
   //Make sure the operating system is running
   tx_interrupt_control(TX_INT_ENABLE);
}


/**
 * @brief Create an event object
 * @param[in] event Pointer to the event object
 * @return The function returns TRUE if the event object was successfully
 *   created. Otherwise, FALSE is returned
 **/

bool_t osCreateEvent(OsEvent *event)
{
	UINT status;
	
  //Create a binary semaphore
  status = tx_semaphore_create(&event->handle,"event",0);

  //Check whether the returned handle is valid
  if(status == TX_SUCCESS)
    return TRUE;
  else
    return FALSE;
}


/**
 * @brief Delete an event object
 * @param[in] event Pointer to the event object
 **/

void osDeleteEvent(OsEvent *event)
{
  //Properly dispose the event object
  tx_semaphore_delete(&event->handle);
}


/**
 * @brief Set the specified event object to the signaled state
 * @param[in] event Pointer to the event object
 **/

void osSetEvent(OsEvent *event)
{
   //Set the specified event to the signaled state
   tx_semaphore_put(&event->handle);
}


/**
 * @brief Set the specified event object to the nonsignaled state
 * @param[in] event Pointer to the event object
 **/

void osResetEvent(OsEvent *event)
{
   //Force the specified event to the nonsignaled state
   while(tx_semaphore_get(&event->handle,TX_NO_WAIT) == TX_SUCCESS);
}


/**
 * @brief Wait until the specified event is in the signaled state
 * @param[in] event Pointer to the event object
 * @param[in] timeout Timeout interval
 * @return The function returns TRUE if the state of the specified object is
 *   signaled. FALSE is returned if the timeout interval elapsed
 **/

bool_t osWaitForEvent(OsEvent *event, systime_t timeout)
{
	UINT status;

	//Wait until the specified event is in the signaled state
	if(timeout == INFINITE_DELAY)
	{
		//Infinite timeout period
		status = tx_semaphore_get(&event->handle, TX_WAIT_FOREVER);
	}
	else
	{
		//Wait for the specified time interval
		status = tx_semaphore_get(&event->handle, OS_MS_TO_SYSTICKS(timeout));
	}

	//The return value tells whether the event is set
	if(status == TX_SUCCESS)
     return TRUE;
  else
     return FALSE;
}


/**
 * @brief Set an event object to the signaled state from an interrupt service routine
 * @param[in] event Pointer to the event object
 * @return TRUE if setting the event to signaled state caused a task to unblock
 *   and the unblocked task has a priority higher than the currently running task
 **/

bool_t osSetEventFromIsr(OsEvent *event)
{
  UINT status;

  //Set the specified event to the signaled state
  status = tx_semaphore_put(&event->handle);

  //A higher priority task has been woken?
  if(status == TX_SUCCESS)
     return TRUE;
  else
     return FALSE;
}


/**
 * @brief Create a semaphore object
 * @param[in] semaphore Pointer to the semaphore object
 * @param[in] count The maximum count for the semaphore object. This value
 *   must be greater than zero
 * @return The function returns TRUE if the semaphore was successfully
 *   created. Otherwise, FALSE is returned
 **/

bool_t osCreateSemaphore(OsSemaphore *semaphore, uint_t count)
{
	UINT status;
	
  //Create a semaphore
	status = tx_semaphore_create(&semaphore->handle,"sem",0);
	

  //Check whether the returned handle is valid
  if(status == TX_SUCCESS)
     return TRUE;
  else
     return FALSE;
}


/**
 * @brief Delete a semaphore object
 * @param[in] semaphore Pointer to the semaphore object
 **/

void osDeleteSemaphore(OsSemaphore *semaphore)
{
	//Properly dispose the specified semaphore
	tx_semaphore_delete(&semaphore->handle);
}


/**
 * @brief Wait for the specified semaphore to be available
 * @param[in] semaphore Pointer to the semaphore object
 * @param[in] timeout Timeout interval
 * @return The function returns TRUE if the semaphore is available. FALSE is
 *   returned if the timeout interval elapsed
 **/

bool_t osWaitForSemaphore(OsSemaphore *semaphore, systime_t timeout)
{
  UINT status;

  //Wait until the specified semaphore becomes available
  if(timeout == INFINITE_DELAY)
  {
     //Infinite timeout period
     status = tx_semaphore_get(&semaphore->handle, TX_WAIT_FOREVER);
  }
  else
  {
     //Wait for the specified time interval
     status = tx_semaphore_get(&semaphore->handle, OS_MS_TO_SYSTICKS(timeout));
  }

  //The return value tells whether the semaphore is available
  if(status == TX_SUCCESS)
    return TRUE;
  else
    return FALSE;
}


/**
 * @brief Release the specified semaphore object
 * @param[in] semaphore Pointer to the semaphore object
 **/

void osReleaseSemaphore(OsSemaphore *semaphore)
{
   //Release the semaphore
   tx_semaphore_put(&semaphore->handle);
}


/**
 * @brief Create a mutex object
 * @param[in] mutex Pointer to the mutex object
 * @return The function returns TRUE if the mutex was successfully
 *   created. Otherwise, FALSE is returned
 **/

bool_t osCreateMutex(OsMutex *mutex)
{
  UINT status;

  status = tx_mutex_create(&mutex->handle,"mutex",TX_NO_INHERIT);

  //Check whether the returned handle is valid
  if(status == TX_SUCCESS)
    return TRUE;
  else
    return FALSE;
}


/**
 * @brief Delete a mutex object
 * @param[in] mutex Pointer to the mutex object
 **/

void osDeleteMutex(OsMutex *mutex)
{
	//Properly dispose the specified mutex
	tx_mutex_delete(&mutex->handle);
}


/**
 * @brief Acquire ownership of the specified mutex object
 * @param[in] mutex Pointer to the mutex object
 **/

void osAcquireMutex(OsMutex *mutex)
{
   //Obtain ownership of the mutex object
   tx_mutex_get(&mutex->handle, TX_WAIT_FOREVER);
}


/**
 * @brief Release ownership of the specified mutex object
 * @param[in] mutex Pointer to the mutex object
 **/

void osReleaseMutex(OsMutex *mutex)
{
   //Release ownership of the mutex object
   tx_mutex_put(&mutex->handle);
}


/**
 * @brief Retrieve system time
 * @return Number of milliseconds elapsed since the system was last started
 **/

systime_t osGetSystemTime(void)
{
   systime_t time;

   //Get current tick count
   time = tx_time_get();

   //Convert system ticks to milliseconds
   return OS_SYSTICKS_TO_MS(time);
}


/**
 * @brief Allocate a memory block
 * @param[in] size Bytes to allocate
 * @return A pointer to the allocated memory block or NULL if
 *   there is insufficient memory available
 **/

void *osAllocMem(size_t size)
{
	CHAR *p = TX_NULL;

   //Allocate a memory block
   tx_byte_allocate(thread_get_pool(),(VOID **) &p,size,TX_NO_WAIT);

   //Debug message
   TRACE_DEBUG("Allocating %" PRIuSIZE " bytes at 0x%08" PRIXPTR "\r\n", size, (uintptr_t) p);

   //Return a pointer to the newly allocated memory block
   return p;
}


/**
 * @brief Release a previously allocated memory block
 * @param[in] p Previously allocated memory block to be freed
 **/

void osFreeMem(void *p)
{
   //Make sure the pointer is valid
   if(p != NULL)
   {
      //Debug message
      TRACE_DEBUG("Freeing memory at 0x%08" PRIXPTR "\r\n", (uintptr_t) p);

      //Free memory block
      tx_byte_release(p);
   }
}
